package dimension.starry.item.typeInterface;

import dimension.starry.constants.ArmorExp;
import dimension.starry.constants.ItemQuality;
import dimension.starry.constants.WeaponAttribute;
import dimension.starry.constants.WeaponExp;
import dimension.starry.item.typeInterface.WeaponType.WeaponType;
import dimension.starry.utils.RandomUtil;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ArmorItem;
import net.minecraft.item.ArmorMaterial;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.recipe.Ingredient;
import net.minecraft.sound.SoundEvent;
import net.minecraft.sound.SoundEvents;
import net.minecraft.util.Hand;
import net.minecraft.util.TypedActionResult;
import net.minecraft.world.World;

import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

public abstract class ArmorType extends ArmorItem implements ALLItemType {

    private static final String ARMOR_ATTRIBUTE = "armor_attribute";
    private static final String ARMOR_LEVEL = "armor_level";
    private static final String ARMOR_EXP = "armor_exp";
    private static final String ARMOR_MAIN_ATTRIBUTE = "armor_main_attribute";

    public ArmorType(String name, Type type, Settings settings) {
        super(new ArmorMaterial() {
            @Override
            public int getDurability(Type type) {
                return 0;
            }

            @Override
            public int getProtection(Type type) {
                return 0;
            }

            @Override
            public int getEnchantability() {
                return 0;
            }

            @Override
            public SoundEvent getEquipSound() {
                return SoundEvents.ITEM_ARMOR_EQUIP_GENERIC;
            }

            @Override
            public Ingredient getRepairIngredient() {
                return null;
            }

            @Override
            public String getName() {
                return name;
            }

            @Override
            public float getToughness() {
                return 0;
            }

            @Override
            public float getKnockbackResistance() {
                return 0;
            }
        }, type, settings);
    }

    public static Map<WeaponAttribute, Double> getAttributeMap(ItemStack itemStack) {
        Map<WeaponAttribute, Double> attributeMap = new LinkedHashMap<>();
        if (itemStack.getItem() instanceof ArmorType && itemStack.getNbt() != null) {
            NbtCompound nbtCompound = itemStack.getNbt();
            NbtCompound attributeNbt = nbtCompound.getCompound(ARMOR_ATTRIBUTE);
            Arrays.stream(WeaponAttribute.values())
                .filter(weaponAttribute -> attributeNbt.contains(weaponAttribute.getName()))
                .forEach(weaponAttribute -> attributeMap.put(
                        weaponAttribute,
                        attributeNbt.getDouble(weaponAttribute.getName())
                    )
                );
        }
        return attributeMap;
    }

    public static void putAttributeMap(ItemStack itemStack, Map<WeaponAttribute, Double> attributeMap) {
        if (itemStack.getItem() instanceof ArmorType && itemStack.getNbt() != null) {
            NbtCompound nbtCompound = itemStack.getOrCreateNbt();
            NbtCompound attributeNbt = new NbtCompound();
            attributeMap.forEach((
                (weaponAttribute, value) -> attributeNbt.putDouble(weaponAttribute.getName(), value)
            ));
            nbtCompound.put(ARMOR_ATTRIBUTE, attributeNbt);
            itemStack.setNbt(nbtCompound);
        }
    }

    public static void upgrade(ItemStack itemStack) {
        if (itemStack.getItem() instanceof ArmorType armorItem) {
            NbtCompound nbtCompound = itemStack.getOrCreateNbt();
            int level = nbtCompound.getInt(ARMOR_LEVEL) + 1;
            if (level > armorItem.getMaxLevel()) {
                return;
            }
            nbtCompound.putInt(ARMOR_LEVEL, level);
            if (level == 4 || level == 8 || level == 12 || level == 16 || level == 20) {
                addAttribute(itemStack);
            }
        }
    }

    private static WeaponAttribute getRandomAttributeByType(
        Type type, boolean isMainAttribute, List<WeaponAttribute> source
    ) {
        if (isMainAttribute) {
            if (Type.HELMET.equals(type)) {
                return WeaponAttribute.SMALL_HEALTH;
            } else if (Type.LEGGINGS.equals(type)) {
                return WeaponAttribute.SMALL_DAMAGE;
            } else if (Type.CHESTPLATE.equals(type)) {
                source = source.stream()
                    .filter(weaponAttribute -> !WeaponAttribute.SPEED.equals(weaponAttribute))
                    .collect(Collectors.toList());
            } else if (Type.BOOTS.equals(type)) {
                source = List.of(
                    WeaponAttribute.SMALL_HEALTH,
                    WeaponAttribute.SMALL_DEFENSE,
                    WeaponAttribute.SMALL_DAMAGE,
                    WeaponAttribute.SPEED
                );
            }
        }
        int totalWeight = source.stream().mapToInt(WeaponAttribute::getRandomWeight).sum();
        int random = RandomUtil.randomInt(1, totalWeight + 1), current = 0;
        for (WeaponAttribute weaponAttribute : source) {
            if (random > current && random <= current + weaponAttribute.getRandomWeight()) {
                return weaponAttribute;
            }
            current += weaponAttribute.getRandomWeight();
        }
        throw new RuntimeException(
            String.format("calculate error, weight not in range: total=%d random=%d current=%d", totalWeight, random,
                current));
    }

    private static void addAttribute(ItemStack itemStack) {
        if (itemStack.getItem() instanceof ArmorType armorItem) {
            Map<WeaponAttribute, Double> attributeMap = getAttributeMap(itemStack);
            if (attributeMap.size() < 4) {
                List<WeaponAttribute> filteredAttributes = Arrays.stream(WeaponAttribute.values())
                    .filter(weaponAttribute -> !attributeMap.containsKey(weaponAttribute))
                    .filter(weaponAttribute -> !weaponAttribute.equals(getMainAttribute(itemStack)))
                    .toList();
                WeaponAttribute randomAttribute = getRandomAttributeByType(armorItem.getType(), false,
                    filteredAttributes);
                attributeMap.put(randomAttribute, randomAttribute.getRandomAttrsByQuality(armorItem.getQuality()));
            } else {
                List<WeaponAttribute> filteredAttributes = Arrays.stream(WeaponAttribute.values())
                    .filter(attributeMap::containsKey)
                    .toList();
                WeaponAttribute randomAttribute = getRandomAttributeByType(armorItem.getType(), false,
                    filteredAttributes);
                double value =
                    attributeMap.get(randomAttribute) + randomAttribute.getRandomAttrsByQuality(armorItem.getQuality());
                attributeMap.put(randomAttribute, value);
            }
            putAttributeMap(itemStack, attributeMap);
        }
    }

    public static void generateRandomAttributes(ItemStack itemStack) {
        if (itemStack.getItem() instanceof ArmorType armorItem) {
            NbtCompound nbtCompound = itemStack.getOrCreateNbt();
            List<WeaponAttribute> filteredAttributes = Arrays.stream(WeaponAttribute.values())
                .filter(weaponAttribute -> weaponAttribute.getStartAttrs() != null)
                .toList();
            WeaponAttribute randomAttribute = getRandomAttributeByType(armorItem.getType(), true, filteredAttributes);
            nbtCompound.putString(ARMOR_MAIN_ATTRIBUTE, randomAttribute.getName());
            itemStack.setNbt(nbtCompound);

            int generateCount = 0;
            if (ItemQuality.EXCELLENT.equals(armorItem.getQuality())) {
                generateCount = RandomUtil.randomInt(1, 3);
            } else if (ItemQuality.FINE.equals(armorItem.getQuality())) {
                generateCount = RandomUtil.randomInt(2, 4);
            } else if (ItemQuality.PERFECT.equals(armorItem.getQuality())) {
                generateCount = RandomUtil.randomInt(3, 5);
            }
            for (int i = 0; i < generateCount; i++) {
                addAttribute(itemStack);
            }
        }
    }

    public static int getLevel(ItemStack itemStack) {
        return itemStack.getNbt() == null ? 0 : itemStack.getNbt().getInt(ARMOR_LEVEL);
    }

    public static WeaponAttribute getMainAttribute(ItemStack itemStack) {
        NbtCompound nbtCompound = itemStack.getNbt();
        if (nbtCompound != null && nbtCompound.contains(ARMOR_MAIN_ATTRIBUTE)) {
            return WeaponAttribute.getByName(nbtCompound.getString(ARMOR_MAIN_ATTRIBUTE));
        }
        return null;
    }

    public static double getMainAttributeValue(ItemStack itemStack) {
        if (itemStack.getItem() instanceof ArmorType armorItem) {
            NbtCompound nbtCompound = itemStack.getNbt();
            if (nbtCompound != null && nbtCompound.contains(ARMOR_MAIN_ATTRIBUTE)) {
                WeaponAttribute mainAttribute = WeaponAttribute.getByName(nbtCompound.getString(ARMOR_MAIN_ATTRIBUTE));
                int index;
                if (ItemQuality.EXCELLENT.equals(armorItem.getQuality())) {
                    index = 0;
                } else if (ItemQuality.FINE.equals(armorItem.getQuality())) {
                    index = 1;
                } else if (ItemQuality.PERFECT.equals(armorItem.getQuality())) {
                    index = 2;
                } else {
                    return 0.0;
                }
                if (mainAttribute != null && mainAttribute.getStartAttrs() != null) {
                    return mainAttribute.getStartAttrs().get(index) +
                        mainAttribute.getPerLevelAdds().get(index) * getLevel(itemStack);
                }
            }
        }
        return 0.0;
    }

    private static void setExp(ItemStack itemStack, int count) {
        if (itemStack.getItem() instanceof ArmorType) {
            NbtCompound nbtCompound = itemStack.getOrCreateNbt();
            nbtCompound.putInt(ARMOR_EXP, count);
            itemStack.setNbt(nbtCompound);
        }
    }

    public static int getExp(ItemStack itemStack) {
        if (itemStack.getItem() instanceof ArmorType) {
            NbtCompound compoundNBT = itemStack.getOrCreateNbt();
            return compoundNBT.getInt(ARMOR_EXP);
        }
        return 0;
    }

    public static void addExp(ItemStack itemStack, int count) {
        if (itemStack.getItem() instanceof ArmorType armorItem) {
            int currentExp = getExp(itemStack) + count;
            int needExp = ArmorExp.getNeedExp(armorItem, getLevel(itemStack));
            while (currentExp >= needExp && getLevel(itemStack) < armorItem.getMaxLevel()) {
                currentExp = currentExp - needExp;
                upgrade(itemStack);
                needExp = ArmorExp.getNeedExp(armorItem, getLevel(itemStack));
            }
            setExp(itemStack, currentExp);
        }
    }

    @Override
    public boolean canRepair(ItemStack stack, ItemStack ingredient) {
        return false;
    }

    @Override
    public TypedActionResult<ItemStack> use(
        World world, PlayerEntity user, Hand hand
    ) {
        ItemStack itemStack = user.getStackInHand(hand);
        if (getMainAttribute(itemStack) == null) {
            if (!world.isClient()) {
                generateRandomAttributes(itemStack);
            }
            return TypedActionResult.success(itemStack);
        }
        return this.equipAndSwap(this, world, user, hand);
    }

    public static int getMaxLevel(Item item) {
        if (item instanceof ArmorType armorType) {
            if (ItemQuality.EXCELLENT.equals(armorType.getQuality())) {
                return 12;
            } else if (ItemQuality.FINE.equals(armorType.getQuality())) {
                return 16;
            } else if (ItemQuality.PERFECT.equals(armorType.getQuality())) {
                return 20;
            }
        }
        return 0;
    }

    public int getMaxLevel() {
        return getMaxLevel(this);
    }

}
